//
// Created by LiuYou on 2021/5/7.
//

#include <cstdio>
#include <cstdlib>
#include <fcntl.h>
#include <unistd.h>
#include <cerrno>
#include <csignal>

// TODO 注意: 重要！！！！
//  使用 alarm()函数往往需要一个 标志性的变量！！！！
//static volatile int token = 0;
static volatile sig_atomic_t token = 0;


static void alarmHandler(int s);


static int getBurst();

/**
 * @brief 使用令牌桶来实现SlowCat
 *
 * 令牌桶:
 * 闲着我就攒权限, 我有1秒钟传输10个字节的权限, 那我就攒着我不浪费。
 * 比方, 我3秒钟闲着, 那我就攒下了30个字节的权限。此时突然来了一大堆数据, 那我
 * 就可以瞬间将这一大波数据中的30个字节解决掉, 然后我攒的用完了, 我就只能一秒钟
 * 10个字节这样继续下去。这样叫做令牌桶。
 *
 * 可以想想cache和buffer缓冲区。
 *
 * @return 0
 */
int main(int argc, char** argv) {
    if (argc < 2) {
        std::fprintf(stderr, "Usage: ...\n");
        std::exit(1);
    }

    signal(SIGALRM, alarmHandler);
    alarm(1);

    // TODO 注意: 对信号造成假错这个问题进行处理
    //  使用到了 do-while 语句。
    int sFD{};
    do {
        sFD = open(argv[1], O_RDONLY);
        if (sFD < 0) {
            if (errno != EINTR) {
                std::perror("open()");
                std::exit(1);
            }

        }
    } while (sFD < 0);

    int dFD{1};
/*    do {
        dFD = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0600);
        if (dFD < 0) {
            if (errno != EINTR) {
                // 防止产生内存泄漏, 因为sFD已经成功打开了。
                close(sFD);
                std::perror("open()");
                std::exit(1);
            }
        }
    } while (dFD < 0);*/

    // 这个程序效率的核心问题在这里 BUF_SIZE的长度, 对这个程序的效率影响非常大！！！！
    const int CPS = 10;
//    const int BUF_SIZE = 1024;
    const int BUF_SIZE = CPS;
    // TODO 注意: 重要: 我们要规定桶的上限。桶不能无限大。
    //  你不能一直攒。我们需要规定一个上限来限制 token的值。
    // 这里我规定上限为100个字节。
    getBurst();
    char buf[BUF_SIZE]{};
    int pos{};
    for (;;) {

        // TODO 注意: 这里实现了令牌桶的设计。
        // TODO 注意: 这里有 BUG, 因为操作不原子！
        while (token <= 0) { ;
            pause();
        }
        --token;


#if false
        // TODO 注意: 重要: 这里的 continue有BUG。改进在另外一个分支中。

        // 但是没有必要坚持读够sizeof(buf)个字节, 因为文件本身可能就没有这么多个字节数
        // 所以, 采取的策略是有多少个字节就读多少个字节。
        ssize_t len = read(sFD, buf, sizeof(buf));
        if (len < 0) {
            if (errno == EINTR) {
                continue;
            }
            std::perror("read()");
            break;
        }
        if (len == 0) {
            break;
        }
#else
        // TODO 注意: 改进版
        //  说真的这个BUG, 我是真的发现不了, 而且即使发现了我也不会改！
        ssize_t len{};
        // TODO 注意: 这里就体现了 漏桶！
        while ((len = read(sFD, buf, sizeof(buf))) < 0) {
            if (errno == EINTR)
                continue;
            std::perror("read()");
            break;
        }
        if (len == 0) {
            break;
        }

#endif

        // TODO 注意: 问题在这里, 逻辑错误了, 我没有写 pos = 0; 这条语句！
        // :(
        pos = 0;
        // 坚持写够len个字节
        while (len > 0) {
            ssize_t ret = write(dFD, buf + pos, len);
            if (ret < 0) {
                if (errno == EINTR) {
                    continue;
                }
                std::perror("write()");
                // 这里其实是有内存泄漏的。
                std::exit(1);
            }
            // 这里很精妙
            len -= ret;
            pos += ret;
        }
        // TODO 注意: 这里加上一条 sleep()函数调用就可以完成我们想要实现的效果, 我们要的效果
        //  但是, 不要在程序中使用 sleep()调用。所以要另寻他法！
//        sleep(1);

    }


    close(sFD);

    std::exit(0);
}

int getBurst() {
//    const int BURST = 100;
    return 100;
}


static void alarmHandler(int s) {
    // TODO 注意: 重要: 本次执行 alarm()会触发下一秒的 alarm()。
    //  当执行一次 SIGALRM信号处理函数的时候, 它就会为自己发出下一秒的时钟信号。
    alarm(1);
    ++token;
    if (token > getBurst()) {
        token = getBurst();
    }
}
